package com.yuantu.judiciary.serviceimpl;

import com.yuantu.judiciary.dao.*;
import com.yuantu.judiciary.exception.enums.AccountExceptionEnum;
import com.yuantu.judiciary.exception.enums.InviteCodeExceptionEnum;
import com.yuantu.judiciary.exception.enums.OrganizationExceptionEnum;
import com.yuantu.judiciary.model.domain.*;
import com.yuantu.judiciary.model.enums.AuthResultType;
import com.yuantu.judiciary.model.enums.InviteCodeType;
import com.yuantu.judiciary.model.form.AddOperatorForm;
import com.yuantu.judiciary.model.form.InviteCodeForm;
import com.yuantu.judiciary.model.vo.InviteCodeDivideVO;
import com.yuantu.judiciary.model.vo.InviteRecordDivideVO;
import com.yuantu.judiciary.service.IInviteCodeService;
import com.yuantu.judiciary.service.IOperatorService;
import com.yuantu.judiciary.service.IVipInfoService;
import com.yuantu.judiciary.utils.AssertUtil;
import com.yuantu.judiciary.utils.UrlUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.sql.Timestamp;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 邀请码
 *
 * @author syw
 * @since 2023-08-18
 **/
@Service
@RequiredArgsConstructor
public class InviteCodeServiceImpl implements IInviteCodeService {

    private static final String INVITE_CODE = "NJXY_INVITE_CODE";
    private final IInviteCodeDao inviteCodeDao;
    private final IInviteRecordDao inviteRecordDao;
    private final IAccountDao accountDao;
    private final IOrganizationDao organizationDao;
    private final IOrgVipRecordDao orgVipRecordDao;
    private final IVipInfoService vipInfoService;
    private final IQueryRecordDao queryRecordDao;

    @Override
    public String getRandom() {
        return UrlUtil.getUniqueShortUrl(INVITE_CODE);
    }

    @Override
    public Boolean bindTrial(Long accountId, InviteCodeForm form) {
        AccountDO account = accountDao.findByIdAndDeletedFalse(accountId);
        AssertUtil.assertTrue(!ObjectUtils.isEmpty(account), AccountExceptionEnum.ACCOUNT_NOT_EXIST);
        OrganizationDO organization = organizationDao.findByIdAndDeletedFalse(account.getOrganizationId());
        AssertUtil.assertTrue(!ObjectUtils.isEmpty(organization), OrganizationExceptionEnum.COMPLETE_ORGANIZATION_INFO);

        InviteCodeDO codeInfo = inviteCodeDao.findCodeByInviteCodeAndTypeAndDeletedFalse(form.getCode(), InviteCodeType.TRIAL);
        AssertUtil.assertTrue(!ObjectUtils.isEmpty(codeInfo), InviteCodeExceptionEnum.INVITE_CODE_NOT_EXIST);
        AssertUtil.assertTrue(ObjectUtils.isEmpty(codeInfo.getExpiresTime()) || codeInfo.getExpiresTime().compareTo(new Timestamp(System.currentTimeMillis())) >= 0, InviteCodeExceptionEnum.INVITE_CODE_EXPIRED);

        if (!ObjectUtils.isEmpty(codeInfo.getUseTimes())) {
            List<InviteRecordDO> recordList = inviteRecordDao.findRecordByInviteCodeAndDeletedFalse(form.getCode());
            AssertUtil.assertTrue(CollectionUtils.isEmpty(recordList) || recordList.size() < codeInfo.getUseTimes(), InviteCodeExceptionEnum.INVITE_CODE_INVALID);
        }

        InviteRecordDO codeRecord = inviteRecordDao.findRecordByInviteCodeAndOrganizationIdAndDeletedFalse(form.getCode(), organization.getId());
        AssertUtil.assertTrue(ObjectUtils.isEmpty(codeRecord), InviteCodeExceptionEnum.TRIAL_CODE_USED);
        codeRecord = new InviteRecordDO();
        codeRecord.setType(codeInfo.getType());
        codeRecord.setInviteCode(form.getCode());
        codeRecord.setOrganizationId(organization.getId());
        codeRecord.setInviteTime(new Timestamp(System.currentTimeMillis()));
        inviteRecordDao.save(codeRecord);


        OrgVipRecordDO orgVipRecord = orgVipRecordDao.findRecordByOrganizationIdAndDeletedFalse(organization.getId());
        if (ObjectUtils.isEmpty(orgVipRecord)) {
            orgVipRecord = new OrgVipRecordDO();
            orgVipRecord.setOrganizationId(organization.getId());
            orgVipRecord.setTotalSearchTimes(codeInfo.getVipUseTimes());
            orgVipRecord.setAccountNum(1);
//            Calendar c = Calendar.getInstance();
//            c.setTime(new Timestamp(System.currentTimeMillis()));
//            c.add(Calendar.MONTH, 1);
//            orgVipRecord.setExpiresTime(new Timestamp(c.getTimeInMillis()));
            orgVipRecord.setTrialSearchTimes(codeInfo.getVipUseTimes());
            orgVipRecordDao.save(orgVipRecord);
        } else {
            orgVipRecord.setTotalSearchTimes(orgVipRecord.getTotalSearchTimes() + codeInfo.getVipUseTimes());
            orgVipRecord.setTrialSearchTimes((orgVipRecord.getTrialSearchTimes() == null ? 0 : orgVipRecord.getTrialSearchTimes()) + codeInfo.getVipUseTimes());
            orgVipRecordDao.save(orgVipRecord);
        }
        return true;
    }

    @Override
    public Boolean bindInvite(Long accountId, InviteCodeForm form) {
        AccountDO account = accountDao.findByIdAndDeletedFalse(accountId);
        AssertUtil.assertTrue(!ObjectUtils.isEmpty(account), AccountExceptionEnum.ACCOUNT_NOT_EXIST);
        OrganizationDO organization = organizationDao.findByIdAndDeletedFalse(account.getOrganizationId());
        AssertUtil.assertTrue(!ObjectUtils.isEmpty(organization), OrganizationExceptionEnum.COMPLETE_ORGANIZATION_INFO);
        bindInviteRecord(form.getCode(), organization.getId(), accountId);
        return true;
    }

    @Override
    public void bindInviteRecord(String code, Long organizationId, Long accountId) {
        if (StringUtils.isNotBlank(code)) {
            InviteCodeDO codeInfo = inviteCodeDao.findCodeByInviteCodeAndTypeAndDeletedFalse(code, InviteCodeType.INVITE);
            AssertUtil.assertTrue(!ObjectUtils.isEmpty(codeInfo), InviteCodeExceptionEnum.INVITE_CODE_NOT_EXIST);
            AssertUtil.assertTrue(ObjectUtils.isEmpty(codeInfo.getExpiresTime()) || codeInfo.getExpiresTime().compareTo(new Timestamp(System.currentTimeMillis())) >= 0, InviteCodeExceptionEnum.INVITE_CODE_EXPIRED);

            if (!ObjectUtils.isEmpty(codeInfo.getUseTimes())) {
                List<InviteRecordDO> recordList = inviteRecordDao.findRecordByInviteCodeAndDeletedFalse(code);
                AssertUtil.assertTrue(CollectionUtils.isEmpty(recordList) || recordList.size() < codeInfo.getUseTimes(), InviteCodeExceptionEnum.INVITE_CODE_INVALID);
            }

            List<InviteRecordDO> inviteRecords = inviteRecordDao.findRecordByCreatorIdAndTypeAndDeletedFalse(accountId, InviteCodeType.INVITE);
            if (CollectionUtils.isEmpty(inviteRecords)) {
                InviteRecordDO data = new InviteRecordDO();
                data.setType(codeInfo.getType());
                data.setInviteCode(code);
                data.setOrganizationId(organizationId);
                data.setInviteTime(new Timestamp(System.currentTimeMillis()));
                data.setCreatorId(accountId);
                inviteRecordDao.save(data);

                vipInfoService.addFreeSearchTimes(codeInfo, organizationId);
            } else {
                String inviteCode = inviteRecords.stream().max(Comparator.comparing(InviteRecordDO::getId)).orElse(new InviteRecordDO()).getInviteCode();
                if (code.equals(inviteCode)) {
                    vipInfoService.addFreeSearchTimes(codeInfo, organizationId);
                }
            }
        }
    }

    @Override
    public void addInviteCodeToAllAccountWithoutInviteCode() {
        List<Long> list = inviteCodeDao.getAccountListWithoutInviteCode();
        list.forEach(aLong -> {
            InviteCodeDO inviteCodeDO = new InviteCodeDO();
            inviteCodeDO.setType(InviteCodeType.INVITE);
            inviteCodeDO.setInviteCode(getRandom());
            inviteCodeDO.setOperatorId(aLong);
            inviteCodeDao.save(inviteCodeDO);
        });
    }

    @Override
    public List<InviteCodeDivideVO> queryDivideInfos(String codeInfo, Long accountId) {

        List<InviteCodeDivideVO> result = new ArrayList<>();
        List<InviteCodeDO> inviteCodeInfos = inviteCodeDao.findInfosByOperatorIdInAndTypeAndDeletedFalse(Collections.
                singletonList(accountId), InviteCodeType.DIVIDE_USE);
        if (StringUtils.isNotBlank(codeInfo)) {
            inviteCodeInfos = inviteCodeInfos.stream().filter(s -> codeInfo.equals(s.getInviteCode())).collect(Collectors.toList());
        }
        if (CollectionUtils.isEmpty(inviteCodeInfos)) {
            return result;
        }
        List<String> inviteCodes = inviteCodeInfos.stream().map(InviteCodeDO::getInviteCode).collect(Collectors.toList());
        List<InviteRecordDO> inviteRecordInfos = inviteRecordDao.findRecordByInviteCodeInAndDeletedFalse(inviteCodes);
        Map<String, List<InviteRecordDO>> inviteRecordMap = inviteRecordInfos.stream().collect(Collectors.groupingBy(InviteRecordDO::getInviteCode));
        for (InviteCodeDO inviteCodeInfo : inviteCodeInfos) {
            InviteCodeDivideVO inviteCodeDivide = new InviteCodeDivideVO();
            inviteCodeDivide.setInviteCode(inviteCodeInfo.getInviteCode());
            inviteCodeDivide.setTotalSum(inviteCodeInfo.getUseTimes());
            inviteCodeDivide.setExpireDate(inviteCodeInfo.getExpiresTime());
            List<InviteRecordDO> inviteRecords = inviteRecordMap.getOrDefault(inviteCodeInfo.getInviteCode(), new ArrayList<>());
            int totalUse = 0;
            for (InviteRecordDO record : inviteRecords) {
                Integer useNum = queryRecordDao.countDistinctByCreatorIdAndAuthorizedResultInAndCodeInfoInAndDeletedFalse(record.getCreatorId(),
                        Arrays.asList(AuthResultType.AUTHORIZED, AuthResultType.SEND_MSG, AuthResultType.AUTHORIZING),
                        Collections.singletonList(inviteCodeInfo.getInviteCode()));
                totalUse = totalUse + useNum;
            }
            inviteCodeDivide.setUseNum(totalUse);
            result.add(inviteCodeDivide);
        }
        return result;
    }

    @Override
    public List<InviteRecordDivideVO> queryDivideInfoDetail(String codeInfo) {

        List<InviteRecordDivideVO> result = new ArrayList<>();
        List<InviteRecordDO> inviteRecordInfos = inviteRecordDao.findRecordByInviteCodeInAndDeletedFalse(Collections.singletonList(codeInfo));
        List<Long> organizationIds = inviteRecordInfos.stream().map(InviteRecordDO::getOrganizationId).collect(Collectors.toList());
        Map<Long, OrganizationDO> organizationMap = new HashMap<>(8);
        if (!CollectionUtils.isEmpty(organizationIds)) {
            organizationMap = organizationDao.findByIdInAndDeletedFalse(organizationIds).stream().
                    collect(Collectors.toMap(OrganizationDO::getId, Function.identity()));
        }
        for (InviteRecordDO inviteRecord : inviteRecordInfos) {
            InviteRecordDivideVO inviteRecordDivide = new InviteRecordDivideVO();
            inviteRecordDivide.setInviteCode(inviteRecord.getInviteCode());
            inviteRecordDivide.setTotalSum(inviteRecord.getUseTimes());
            Integer useNum = queryRecordDao.countDistinctByCreatorIdAndAuthorizedResultInAndCodeInfoInAndDeletedFalse(inviteRecord.getCreatorId(),
                    Arrays.asList(AuthResultType.AUTHORIZED, AuthResultType.SEND_MSG, AuthResultType.AUTHORIZING),
                    Collections.singletonList(codeInfo));
            inviteRecordDivide.setEnterName(organizationMap.getOrDefault(inviteRecord.getOrganizationId(), new OrganizationDO()).getEnterName());
            inviteRecordDivide.setUseNum(useNum);
            result.add(inviteRecordDivide);
        }
        return result;
    }
}
